#ifndef ANN_CPP
#define ANN_CPP

#include "ann.h"

void Ann::activeCell(int x) {
	cells[x].voltage = 0.0f;
	cells[x].last_active_time = time;
	for (unsigned i = 0; i < cells[x].out_edge_ids.size(); i++) {
		int edge_id = cells[x].out_edge_ids[i];
		int y = edges[edge_id].to;
		float weight = edges[edge_id].weight;
		if (cells[y].last_stimulate_time == time && cells[y].last_deal_time != time) // 满足此条件则说明：目标神经元与目前神经元位于同一层，且未被处理（还未从队列中取出）；须先将电压值写入到缓冲区
			cells[y].temp_voltage += weight;
		else if (cells[y].last_stimulate_time == time + 1) // 满足此条件则说明：目标神经元已经被同层的其他神经元触发过；无须再次入队
			cells[y].voltage += weight;
		else {
			cells[y].voltage *= cells[y].reduce.calc(time - cells[y].last_stimulate_time) / (cells[y].reduce.a + cells[y].reduce.b);
			cells[y].voltage += weight;
			cells[y].last_stimulate_time = time + 1;
			q.push(y);
		}
	}
}

Ann::Ann(const char *filename, int timemout) : time(0), timeout(timemout) {
	FILE *fp = fopen(filename, "r");
	// 读取神经元
	int n;
	fscanf(fp, "%d", &n);
	for (int i = 0; i < n; i++) {
		Cell new_cell;
		fscanf(fp, "%d", &new_cell.category);
		fscanf(fp, "%f%f%f", &new_cell.threshold.a, &new_cell.threshold.k, &new_cell.threshold.b);
		fscanf(fp, "%f%f%f", &new_cell.reduce.a, &new_cell.reduce.k, &new_cell.reduce.b);
		cells.push_back(new_cell);
	}
	// 读取边
	int m;
	fscanf(fp, "%d", &m);
	for (int i = 0; i < m; i++) {
		Edge new_edge;
		fscanf(fp, "%d%d%f", &new_edge.from, &new_edge.to, &new_edge.weight);
		edges.push_back(new_edge);
	}
	fclose(fp);
	buildAssistArr();
}

Ann::Ann(int max_cell_num, int max_edge_num,
	int input_cell_num, int output_cell_num, 
	int timeout) : time(0), timeout(timeout) {
	// 建神经元
	for (int i = 1; i <= max_cell_num; i++) {
		Cell new_cell;
		new_cell.init();
		cells.push_back(new_cell);
	}
	// 建边
	for (int i = 1; i <= max_edge_num; i++) {
		int from, to;
		do {
			from = random(0, cells.size()-1), to = random(0, cells.size()-1);
		} while(from == to);
		Edge new_edge(from, to, normal());
		edges.push_back(new_edge);
	}
	// 挑出一些神经元作为输入层
	for (int i = 1; i <= input_cell_num; i++) {
		int id;
		do {
			id = random(0, cells.size()-1);
		} while(cells[id].category != 0);
		cells[id].category = -1;
	}
	buildAssistArr();
	// 挑出一些神经元作为输出层
	for (int i = 1; i <= output_cell_num; i++) {
		int id;
		do {
			id = random(0, cells.size()-1);
		} while(cells[id].category != 0);
		cells[id].category = i;
	}
}

void Ann::buildAssistArr() {
	input_cell_ids.clear();
	for (unsigned i = 0; i < cells.size(); i++) {
		cells[i].in_edge_ids.clear();
		cells[i].out_edge_ids.clear();
		if (cells[i].category == -1)
			input_cell_ids.push_back(i);
	}
	for (unsigned i = 0; i < edges.size(); i++) {
		cells[edges[i].from].out_edge_ids.push_back(i);
		cells[edges[i].to].in_edge_ids.push_back(i);
	}
}

void Ann::clearMemory() {
	time = 0;
	while (!q.empty())
		q.pop();
	for (unsigned i = 0; i < cells.size(); i++)
		cells[i].clearMemory();
}

bool Ann::isOverThreshold(int x) {
	float threshold = cells[x].threshold.calc(time - cells[x].last_active_time);
	if (cells[x].voltage >= threshold)
		return true;
	else
		return false;
}

int Ann::run(bool input_data[]) {
	time++;
	if (time > timeout)
		return -1;
	for (unsigned i = 0; i < input_cell_ids.size(); i++)
		if (input_data[i])
			activeCell(input_cell_ids[i]);
	while (!q.empty()) {
		int id = q.front();
		q.pop();
		if (time == cells[id].last_stimulate_time-1)
			time++;
		if(time > timeout)
			return -1;
		if (cells[id].category >= 1)
			return cells[id].category;
		cells[id].last_deal_time = time;
		if (isOverThreshold(id)) {
			activeCell(id);
		}
		cells[id].voltage += cells[id].temp_voltage;
		cells[id].temp_voltage = 0.0;
	}
	return run(input_data); // 再此运行，直到有输出或超时
}

void Ann::saveParam(const char *filename) {
	FILE *fp = fopen(filename, "w");
	// 保存神经元
	fprintf(fp, "%llu\n", cells.size());
	for (unsigned i = 0; i < cells.size(); i++) {
		fprintf(fp, "%d\n", cells[i].category);
		fprintf(fp, "%f %f %f\n", cells[i].threshold.a, cells[i].threshold.k, cells[i].threshold.b);
		fprintf(fp, "%f %f %f\n", cells[i].reduce.a, cells[i].reduce.k, cells[i].reduce.b);
	}
	// 保存边
	fprintf(fp, "%llu\n", edges.size());
	for (unsigned i = 0; i < edges.size(); i++)
		fprintf(fp, "%d %d %f\n", edges[i].from, edges[i].to, edges[i].weight);
	fclose(fp);
}

void Ann::variateCell(float rate) {
	for (unsigned i = 0; i < cells.size(); i++) {
		if (chance(rate))
			cells[i].threshold.a *= exp(normal());
		if (chance(rate))
			cells[i].threshold.k *= exp(normal());
		if (chance(rate))
			cells[i].threshold.b *= exp(normal());
		if (chance(rate))
			cells[i].reduce.a *= exp(normal());
		if (chance(rate))
			cells[i].reduce.k *= exp(normal());
		if (chance(rate))
			cells[i].reduce.b *= exp(normal());
	}
}

void Ann::variateEdge(float rate) {
	for (unsigned i = 0; i < edges.size(); i++) {
		if (chance(rate)) {
			int from, to;
			do {
				from = random(0, cells.size()-1), to = random(0, cells.size()-1);
			} while(from == to);
			edges[i] = Edge(from, to, normal());
		}
	}
	buildAssistArr();
}

#endif